
const PENDING = 'PENDING';
const RESOLVE = 'FULFILLED';
const REJECT = 'REJECTED'

function resolvePromise(x, promise2, resolve, reject) {
    if (x === promise2) {
        return reject(new Error('chain cycle'))
    }

    let called = false;
 
    if (typeof x === 'object' && x !== null || typeof x === 'function') {
        try {
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, y => {
                    if (called) return
                    called = true;
                    resolvePromise(y, promise2, resolve, reject);
                }, r => {
                    if (called) return
                    called = true;

                    reject(r)
                })
            } else {
                resolve(x)
            }
        }
        catch (err) {
            if (called) return
            called = true
            reject(err)
        }
    } else {
        resolve(x)
    }
}

class MyPromise {
    constructor(executor) {

        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.onResolvedCallbacks = [];
        this.onRejectedCallbacks = [];

        const resolve = (value) => {
            if (this.status === PENDING) {
                this.status = RESOLVE;
                this.value = value;

                this.onResolvedCallbacks.forEach(fn => fn());
            }
        }

        const reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECT;
                this.reason = reason;

                this.onRejectedCallbacks.forEach(fn => fn());
            }
        }

        try {
            executor(resolve, reject)
        } catch (err) {
            reject(err)
        }
    }

    then(onResolve, onReject) {
        onResolve = typeof onResolve === 'function' ? onResolve : value => value;
        onReject = typeof onReject === 'function' ? onReject : reason => { throw reason };

        const promise2 = new Promise((resolve, reject) => {
            if (this.status === PENDING) {
                this.onResolvedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onResolve(this.value);
                            resolvePromise(x, promise2, resolve, reject)
                        } catch (err) {
                            reject(err)
                        }
                    })
                })

                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onReject(this.reason);
                            resolvePromise(x, promise2, resolve, reject)
                        } catch (err) {
                            reject(err)
                        }
                    })
                })
            }

            if (this.status === RESOLVE) {
                setTimeout(() => {
                    try {
                        let x = onResolve(this.value);
                        resolvePromise(x, promise2, resolve, reject)
                    } catch (err) {
                        reject(err)
                    }
                })
            }

            if (this.status === REJECT) {
                try {
                    let x = onReject(this.reason);
                    resolvePromise(x, promise2, resolve, reject)
                } catch (err) {
                    reject(err)
                }
            }
        })

        return promise2
    }
}

let p = new MyPromise((resolve) => {
    resolve(1)
}).then(v => {
    return {
        then: function (onFulfilled) {
            onFulfilled(20)
            onFulfilled(3)
        }
    }
}).then(v => {
    console.log(v)
})

MyPromise.all = function (promiseArr) {
    const len = promiseArr.length;
    let count = 0;
    let result = [];
    return new Promise((resolve, reject) => {
        function handleAction(x, i) {
            result[i] = x;
            count++;
            if (count === len) {
                resolve(result)
            }
        }

        for (let i = 0; i < len; i++) {
            const p = promiseArr[i];
            if (isPromise(p)) {
                p.then((val) => {
                    handleAction(val, i);
                }, r => reject(r))
            } else {
                handleAction(p, i);
            }
        }
    })
}

MyPromise.race = function (promiseArr) {
    const len = promiseArr.length;

    return new Promise((resolve, reject) => {
        for (let i = 0; i < len; i++) {
            const p = promiseArr[i];
            if (isPromise(p)) {
                p.then((val) => {
                    resolve(val)
                }, r => reject(r))
            } else {
                resolve(p)
            }
        }
    })
}

MyPromise.prototype.finally = function (callback) {
    let P = this.constructor;
    return this.then(
        value => P.resolve(callback()).then(() => value),
        reason => P.resolve(callback()).then(() => { throw reason })
    );
};

MyPromise.prototype.any = function (promiseArr) {
    let len = promiseArr.length;
    let errs = [];

    return new Promise((resolve, reject) => {
        if (len === 0) {
            return reject('All promises were rejected')
        }
        for (let i = 0; i < len; i++) {
            let p = promiseArr[i];
            p.then(val => {
                resolve(val)
            }, err => {
                len--;
                errs.push(err)
                if (len === 0) {
                    reject(errs)
                }
            })
        }
    })
}

MyPromise.allSettled = function (promiseArr) {
    let len = promiseArr.length;
    let result = [];
    return new Promise((resolve, reject) => {
        for (let i = 0; i < len; i++) {
            let p = promiseArr[i];
            p.then(val => {
                result[i] = { status: 'fulfilled', value: val }
            }, err => {
                result[i] = { status: 'rejected', reason: err }
            }).finally(function () {
                len--;
                if (len === 0) {
                    resolve(result)
                }
            })
        }
    })
}

function isPromise(x) {
    if (typeof x === 'object' && x !== null || typeof x === 'function') {
        let then = x.then;
        if (typeof then === 'function') {
            return true;
        }
    }

    return false;
}

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        // resolve('hello')
        reject('err2')
    }, 2000)
})

let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        // resolve('hello')
        reject('err3')
    }, 3000)
})

// MyPromise.all([1, 2, 3, p1]).then((val) => {
//     console.log(val)
// }).catch(err => {
//     console.log(err)
// })

MyPromise.race([p2, p1]).then((val) => {
    console.log('race', val)
}).catch(err => {
    console.log('race', err)
})
